/*
 * SoapUI, Copyright (C) 2004-2017 SmartBear Software
 *
 * Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent 
 * versions of the EUPL (the "Licence"); 
 * You may not use this work except in compliance with the Licence. 
 * You may obtain a copy of the Licence at: 
 * 
 * http://ec.europa.eu/idabc/eupl 
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the Licence is 
 * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
 * express or implied. See the Licence for the specific language governing permissions and limitations 
 * under the Licence. 
 */

package com.eviware.soapui.impl.wsdl.panels.teststeps;

import com.eviware.soapui.SoapUI;
import com.eviware.soapui.impl.wsdl.panels.teststeps.support.NamedParameterStatement;
import com.eviware.soapui.impl.wsdl.teststeps.JdbcRequestTestStep;
import com.eviware.soapui.impl.wsdl.teststeps.WsdlTestStepWithProperties;
import com.eviware.soapui.model.iface.Request;
import com.eviware.soapui.model.iface.Submit;
import com.eviware.soapui.model.iface.SubmitContext;
import com.eviware.soapui.model.iface.SubmitListener;
import com.eviware.soapui.model.propertyexpansion.PropertyExpander;
import com.eviware.soapui.model.testsuite.TestProperty;
import com.eviware.soapui.support.SoapUIException;
import com.eviware.soapui.support.StringUtils;
import com.eviware.soapui.support.jdbc.JdbcUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.concurrent.Future;

public class JdbcSubmit implements Submit, Runnable {
    public static final String JDBC_ERROR = "JDBC_ERROR";
    public static final String JDBC_TIMEOUT = "JDBC_TIMEOUT";
    private volatile Future<?> future;
    private SubmitContext context;
    private Status status;
    private SubmitListener[] listeners;
    private Exception error;
    private long timestamp;
    protected ResultSet resultSet;
    protected PreparedStatement statement;
    private Connection connection;
    private long timeTaken;
    private final JdbcRequest request;
    private JdbcResponse response;
    private String rawSql;

    public JdbcSubmit(JdbcRequest request, SubmitContext submitContext, boolean async) {
        this.request = request;
        this.context = submitContext;

        List<SubmitListener> regListeners = SoapUI.getListenerRegistry().getListeners(SubmitListener.class);

        SubmitListener[] submitListeners = request.getSubmitListeners();
        this.listeners = new SubmitListener[submitListeners.length + regListeners.size()];
        for (int c = 0; c < submitListeners.length; c++) {
            this.listeners[c] = submitListeners[c];
        }

        for (int c = 0; c < regListeners.size(); c++) {
            this.listeners[submitListeners.length + c] = regListeners.get(c);
        }

        error = null;
        status = Status.INITIALIZED;

        if (async) {
            future = SoapUI.getThreadPool().submit(this);
        } else {
            run();
        }
    }

    public void cancel() {
        if (status == Status.CANCELED) {
            return;
        }

        JdbcRequest.logger.info("Canceling request..");
        if (status == Status.RUNNING) {
            cancelQuery();
        }

        status = Status.CANCELED;

        for (int i = 0; i < listeners.length; i++) {
            try {
                listeners[i].afterSubmit(this, context);
            } catch (Throwable e) {
                SoapUI.logError(e);
            }
        }
    }

    public Exception getError() {
        return error;
    }

    public Request getRequest() {
        return request;
    }

    public JdbcResponse getResponse() {
        return response;
    }

    public Status getStatus() {
        return status;
    }

    public Status waitUntilFinished() {
        if (future != null) {
            if (!future.isDone()) {
                try {
                    future.get();
                } catch (Exception e) {
                    SoapUI.logError(e);
                }
            }
        } else {
            throw new RuntimeException("cannot wait on null future");
        }

        return getStatus();
    }

    public void run() {
        try {
            for (int i = 0; i < listeners.length; i++) {
                if (!listeners[i].beforeSubmit(this, context)) {
                    status = Status.CANCELED;
                    System.err.println("listener cancelled submit..");
                    return;
                }
            }

            status = Status.RUNNING;
            runQuery();

            if (status != Status.CANCELED) {
                status = Status.FINISHED;
            }
        } catch (Exception e) {
            SoapUI.logError(e);
            error = e;
        } finally {
            if (error != null) {
                status = Status.ERROR;
            }

            if (status != Status.CANCELED) {
                for (int i = 0; i < listeners.length; i++) {
                    try {
                        listeners[i].afterSubmit(this, context);
                    } catch (Throwable e) {
                        SoapUI.logError(e);
                    }
                }
            }
        }
    }

    private void runQuery() throws Exception {
        prepare();
        load();
        createResponse();
    }

    public void cancelQuery() {
        try {
            if (statement != null) {
                statement.cancel();
            }
        } catch (SQLException ex) {
            SoapUI.logError(ex);
        }
    }

    private void getDatabaseConnection() throws SQLException, SoapUIException {
        JdbcRequestTestStep testStep = request.getTestStep();
        connection = JdbcUtils.initConnection(context, testStep.getDriver(), testStep.getConnectionString(),
                testStep.getPassword());
        // IMPORTANT: setting as readOnly raises an exception in calling stored
        // procedures!
        // connection.setReadOnly( true );
    }

    private void load() throws Exception {
        try {
            JdbcRequestTestStep testStep = request.getTestStep();

            if (testStep.isStoredProcedure()) {
                timestamp = System.currentTimeMillis();
                statement.execute();
            } else {
                timestamp = System.currentTimeMillis();
                statement.execute();
            }
            timeTaken = System.currentTimeMillis() - timestamp;
            if (!StringUtils.isNullOrEmpty(request.getTimeout()) && timeTaken > Long.parseLong(request.getTimeout())) {
                context.setProperty(JDBC_TIMEOUT, PropertyExpander.expandProperties(context, request.getTimeout()));
            }
        } catch (SQLException e) {
            context.setProperty(JDBC_ERROR, e);
            throw e;
        } finally {
            timeTaken = System.currentTimeMillis() - timestamp;
        }
    }

    private void prepare() throws Exception {
        JdbcRequestTestStep testStep = request.getTestStep();
        getDatabaseConnection();
        List<TestProperty> props = testStep.getPropertyList();
        if (testStep.isStoredProcedure()) {
            rawSql = PropertyExpander.expandProperties(context, testStep.getQuery());

            if (!rawSql.startsWith("{call ") && !rawSql.endsWith("}")) {
                rawSql = "{call " + rawSql + "}";
            }

        } else {
            rawSql = PropertyExpander.expandProperties(context, testStep.getQuery());
        }
        NamedParameterStatement p = new NamedParameterStatement(connection, rawSql);
        for (TestProperty testProperty : props) {
            String value = PropertyExpander.expandProperties(context, testProperty.getValue());
            if (!testProperty.getName().equals(WsdlTestStepWithProperties.RESPONSE_AS_XML)) {
                p.setString(testProperty.getName(), value);
            }
        }
        statement = p.getStatement();

        try {
            if (!StringUtils.isNullOrEmpty(testStep.getQueryTimeout())) {
                String queryTimeout = PropertyExpander.expandProperties(testStep, testStep.getQueryTimeout());
                statement.setQueryTimeout(Integer.parseInt(queryTimeout));
            }
        } catch (NumberFormatException e) {
            SoapUI.logError(e, "Problem setting timeout");
        }

        try {
            if (!StringUtils.isNullOrEmpty(testStep.getMaxRows())) {
                String maxRows = PropertyExpander.expandProperties(testStep, testStep.getMaxRows());
                statement.setMaxRows(Integer.parseInt(maxRows));
            }
        } catch (NumberFormatException e) {
            SoapUI.logError(e, "Problem setting maxRows");
        }
        try {
            if (!StringUtils.isNullOrEmpty(testStep.getFetchSize())) {
                String fetchSize = PropertyExpander.expandProperties(testStep, testStep.getFetchSize());
                statement.setFetchSize(Integer.parseInt(fetchSize));
            }
        } catch (NumberFormatException e) {
            SoapUI.logError(e, "Problem setting fetchSize");
        }
    }

    public String getRawSql() {
        return rawSql;
    }

    protected String createResponse() {
        try {
            response = new JdbcResponse(request, statement, rawSql);
            response.setTimestamp(timestamp);
            response.setTimeTaken(timeTaken);
        } catch (Exception e) {
            SoapUI.logError(e);
        } finally {
            try {
                if (connection != null) {
                    connection.close();
                }
                if (statement != null) {
                    statement.close();
                }
                if (resultSet != null) {
                    resultSet.close();
                }
            } catch (Exception e) {
            }
        }
        return null;
    }
}
